package com.kaibes.core.base.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ClassHelper {
    
    public static Object invoke(Method method, Object receiver, Object... args) {
        try {
            method.setAccessible(true);
            Object o = method.invoke(receiver, args);
            return o;
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (IllegalArgumentException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
    
    public static List<Method> filterMethods(Class<?> clazz, Class<?>... parameterTypes) {
        Method[] methods = clazz.getDeclaredMethods();
        List<Method> methodList = new ArrayList<>();
        for (Method method:methods) {
            Class<?>[] requests = method.getParameterTypes();
            if (requests.length == parameterTypes.length) {
                boolean compare = true;
                for (int i = 0; i < requests.length; i++) {
                    if (requests[i] != parameterTypes[i]) {
                        compare = false;
                        break;
                    }
                }
                if (compare) {
                    methodList.add(method);
                }
            }
        }
        return methodList;
    }
    
    public static List<Method> filterMethodsOneParameterByReturn(Class<?> clazz, Class<?> returnType) {
        Method[] methods = clazz.getDeclaredMethods();
        List<Method> methodList = new ArrayList<>();
        for (Method method:methods) {
            Class<?>[] types = method.getParameterTypes();
            if (types.length == 1 && method.getReturnType() == returnType) {
                methodList.add(method);
            }
        }
        return methodList;
    }
    
    public static Method filterMethodsOneParameterByReturnOne(Class<?> clazz, Class<?> returnType) {
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method:methods) {
            Class<?>[] types = method.getParameterTypes();
            if (types.length == 1 && method.getReturnType() == returnType) {
                return method;
            }
        }
        return null;
    }
    
    public static void filterMethodsOneParameterByReturn(Class<?> clazz, Class<?> returnType, MethodTask task) {
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method:methods) {
            Class<?>[] types = method.getParameterTypes();
            if (types.length == 1 && method.getReturnType() == returnType) {
                task.onDoTask(method);
            }
        }
    }
    
    public static Method filterVoidMethodOneParameterAssignable(Class<?> clazz, Class<?> assigable) {
        Method[] methods = clazz.getDeclaredMethods();
        Method target = null;
        for (Method method:methods) {
            Class<?>[] types = method.getParameterTypes();
            if (types.length == 1 && method.getReturnType() == void.class && assigable.isAssignableFrom(types[0])) {
                target = method;
                break;
            }
        }
        return target;
    }
    
    public static Type[] findParamterType(Class<?> clazz, Class<?> returnType) {
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method:methods) {
            if (method.getReturnType() == returnType) {
                return method.getGenericParameterTypes();
            }
        }
        return null;
    }
    
    public static Type[] findParamterType(Class<?> clazz, Class<?> returnType, int paramterSize) {
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method:methods) {
            Type[] types = method.getParameterTypes();
            if (types.length == paramterSize && method.getReturnType() == returnType) {
                return method.getGenericParameterTypes();
            }
        }
        return null;
    }
    
    public static Class<?>[] findParamterClass(Class<?> clazz, Class<?> returnType, int paramterSize) {
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method:methods) {
            Class<?>[] types = method.getParameterTypes();
            if (types.length == paramterSize && method.getReturnType() == returnType) {
                return types;
            }
        }
        return null;
    }
    
    public static Class<?> findParamterOneClassOne(Class<?> clazz, Class<?> returnType) {
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method:methods) {
            Class<?>[] types = method.getParameterTypes();
            if (types.length == 1 && method.getReturnType() == returnType) {
                return types[0];
            }
        }
        return null;
    }
    
    public static List<Class<?>> findParamterOneClass(Class<?> clazz, Class<?> returnType) {
        Method[] methods = clazz.getDeclaredMethods();
        List<Class<?>> clazzList = new ArrayList<>();
        for (Method method:methods) {
            Class<?>[] types = method.getParameterTypes();
            if (types.length == 1 && method.getReturnType() == returnType) {
                clazzList.add(types[0]);
            }
        }
        return clazzList;
    }
    
    public static void setField(Field field, Object obj, Object value) {
        try {
            field.setAccessible(true);
            field.set(obj, value);
        } catch (IllegalArgumentException|IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    

    public static ArrayList<Field> getAllFields(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        ArrayList<Field> fieldsList = new ArrayList<>();
        ArrayList<Field> fieldsListTemp = new ArrayList<>();
        for (Field field : fields) {
            if (field.getAnnotation(NotReflection.class) == null) {
                fieldsList.add(field);
            }
        }
        if (clazz.getSuperclass() != Object.class) {
            fieldsListTemp = getAllFields(clazz.getSuperclass());
        }
        fieldsList.addAll(0, fieldsListTemp);
        fieldsList.trimToSize();
        return fieldsList;
    }
    
    public static void convertBean2Bean(ClassReflectionable from, ClassReflectionable to) {
        ClassReflection fromReflection = from.getReflection();
        ClassReflection toReflection = to.getReflection();
        String[] toFieldNameArr = to.getReflection().getFieldNameArr();
        for (String fieldName : toFieldNameArr) {
            toReflection.setValue(to, fieldName, fromReflection.getValue(from, fieldName));
        }
    }
    
    public static HashMap<String, Method> createMethodMap(Class<?> clazz, String head) {
        Method[] methods = clazz.getDeclaredMethods();
        HashMap<String, Method> methodMap = new HashMap<>();
        for (Method method : methods) {
            if (method.getName().startsWith(head)) {
                methodMap.put(method.getName(), method);
            }
        }
        return methodMap;
    }
    public static HashMap<String, Method> createMethodMap(Class<?> clazz) {
        Method[] methods = clazz.getDeclaredMethods();
        HashMap<String, Method> methodMap = new HashMap<>();
        for (Method method : methods) {
            methodMap.put(method.getName(), method);
        }
        return methodMap;
    }
    
    public static <C> C createInstance(Class<C> clazz) {
        try {
            return clazz.getDeclaredConstructor().newInstance();
        } catch (InstantiationException|IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public static Object createInstance(String clazzString) {
        try {
            Class<?> clazz = Class.forName(clazzString);
            return clazz.getDeclaredConstructor().newInstance();
        } catch (ClassNotFoundException|InstantiationException|IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    @SuppressWarnings("unchecked")
    public static <T> T createInstance(TypeReference<T> typeReference) {
        try {
            String clazzString = typeReference.getType().getTypeName();
            Pattern p = Pattern.compile("([^<]+)<(.+)>$");
            Matcher m = p.matcher(clazzString);
            if (m.matches()) {
                Class<?> clazz = Class.forName(m.group(1));
                Object o = clazz.getDeclaredConstructor().newInstance();
                return (T) o;
            } else {
                return (T) createInstance(clazzString);
            }
        } catch (ClassNotFoundException|InstantiationException|IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    public static <C> C createInstance(Class<C> clazz, Object... initargs) {
        Class<?>[] clazzs = new Class[initargs.length];
        for (int i = 0; i < clazzs.length; i++) {
            clazzs[i] = initargs[i].getClass();
        }
        return createInstance(clazz, clazzs, initargs);
    }
    
    public static boolean hasMethod(Class<?> clazzA, Class<?> clazzB) {
        Method[] methodsB = clazzB.getMethods();
        for (Method method : methodsB) {
            try {
                clazzA.getMethod(method.getName(), method.getParameterTypes());
            } catch (NoSuchMethodException | SecurityException e) {
                return false;
            }
        }
        return true;
    }
    
    public static <C> C createInstance(Class<C> clazz, Class<?>[] clazzs, Object[] initargs) {
        try {
            Constructor<C> constructor = clazz.getConstructor(clazzs);
            C cobj = constructor.newInstance(initargs);
            return cobj;
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static <C> C createInstance(Class<C> clazz, Class<?> clazzs, Object initargs) {
        try {
            Constructor<C> constructor = clazz.getConstructor(clazzs);
            C cobj = constructor.newInstance(initargs);
            return cobj;
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
}
